/*
 * Created on 2010-11-02
 * The main class to generate messages.
 */
package field;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.TreeMap;

import messages.Msg_o2s_enterCell;
import messages.Msg_o2s_focalMove;
import messages.Msg_o2s_installQuery;
import messages.Msg_o2s_velocityChange;
import messages.Msg_s2o_focalMove;
import messages.Msg_s2o_queryResult;
import messages.Msg_s2o_velocityChange;

/**
 * 1.events are kept in myeventq in MessageGenerator.java <br>
 * instead of MovingObject.java to save memory [done]<br>
 * 2. test MyEventQ.java [done] <br>
 * 3. test message generator [done] <br>
 * 4. check writeo2s message, duplicate time points[done]<br>
 * 5. check the number of messages (incoming and outgoing) are correct<br>
 * 6. Circle implementation is incorrect. correct it.[done] <br>
 * 7. check eventQ is correct<br>
 * 
 * @author Xiaohui
 * 
 */
public class MessageGeneratorCentral {
	double cellLength;
	int currentTS;
	boolean end;
	Field field;
	String[] lastORecord;
	int numObjects;
	int numQueries;
	HashMap<Integer, MovingObject> objects;
	// HashMap<Integer, MovingObject> q_objects;

	File ofilePath;
	File o2sMsgFilePath;
	BufferedReader obr;
	BufferedWriter o2sMsgBw;// write out o2s messages.

	Statistics stat;
	String speedTag;
	TreeMap<Integer, ArrayList<String>> timeMsgMap_o2s;
	TreeMap<Integer, ArrayList<String>> timeMsgMap_s2o;
	HashSet<Integer> enterNewCellMo;

	/**
	 * the sorted linked list queue to store all events; it should be in every
	 * moving object, but for the simulation, we create here.
	 * 
	 */
	MyEventQSorted eventQ;

	/**
	 * 
	 * 1. read data for this time stamp <br>
	 * 2. Update moving objects' location and velocity. <br>
	 * 3. record which objects should be treated <br>
	 */
	public void computeVelocity() {
		// clear the enter new cell first
		enterNewCellMo.clear();
		// query objects
		String[] aORecord = this.lastORecord;
		int readTS = Integer.parseInt(aORecord[4]);
		while (aORecord != null && readTS == currentTS) {
			if (aORecord[0].equals("disappearpoint")) {
				// do nothing, since a newpoint record of this object will come.
			} else {
				int qid = Integer.parseInt(aORecord[1]);
				double px = Double.parseDouble(aORecord[5]);
				double py = Double.parseDouble(aORecord[6]);
				MovingObject qmo = this.objects.get(qid);
				qmo.nextP = new Point2D.Double(px, py);

				// calculate the speed
				double deltaX = qmo.nextP.getX() - qmo.getCenterX();
				double deltaY = qmo.nextP.getY() - qmo.getCenterY();
				Velocity vel = new Velocity(deltaX, deltaY); // because delta
				// time step is 1.
				if (qmo.getVelocity().sameWith(vel)) {
					qmo.setVelocity(vel);
					// doing nothing
				} else {
					// speed change is significant
					Velocity oldv = qmo.speed;
					// velocity has changed
					qmo.setVelocity(vel);
					// handling velocity change
					// update eventQ 
					this.handleFocalVelocityChange(currentTS, qmo);

					// make it know to the mcq's
					//update eventQ
					handleMOVelocityChange(qmo, oldv, vel);
				}
				// record migrate moving objects to handle at processNTS()
				if (!field.inWhichCell(qmo.getCenterP()).equals(
						field.inWhichCell(qmo.nextP))) {
					enterNewCellMo.add(qmo.oid);
				}
			}
			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
	}

	public String[] getOneObjectRecord() {
		String line = null;
		try {
			line = this.obr.readLine();
			if (line != null) {
				return line.split("\t");
			} else {
				this.end = true;
				return null;
			}
		} catch (IOException e) {
			System.out.println("in time0process, IOException");
			System.exit(-1);
		}
		return null;
	}

	// public String[] getOneQueryRecord() {
	// String line = null;
	// try {
	// line = this.qbr.readLine();
	// if (line != null) {
	// return line.split("\t");
	// } else {
	// return null;
	// }
	// } catch (IOException e) {
	// System.out.println("in time0process, IOException");
	// System.exit(-1);
	// }
	// return null;
	// }

	public MovingObject getObject(int oid) {
		return this.objects.get(oid);
	}

	// public MovingObject getQ_object(int oid) {
	// return this.q_objects.get(oid);
	// }

	/**
	 * When a cell moves from a cell (pre) to a new cell (curr), <br>
	 * 1) the moving object sends its current location and request for list of
	 * queries in the curr cell <br>
	 * 2) for each q \in list of queries <br>
	 * if mo satisfies q <br>
	 * add mo to q <br>
	 */

	private void handleEnterCell(MovingObject mo, Cell prevCell, Cell currCell) {
		// remove mo from prevCell, add to currCell
		prevCell.registeredObjects.remove(mo.oid);
		currCell.registeredObjects.add(mo.oid);

		// generate messages
		Msg_o2s_enterCell mec = new Msg_o2s_enterCell(this.currentTS, 0,
				mo.oid, prevCell.getCellId(), currCell.getCellId());

		Msg_s2o_queryResult mqr = new Msg_s2o_queryResult(currentTS);
		this.putMsgIntoMap(currentTS, mec.toString(), true);
		this.putMsgIntoMap(currentTS, mqr.toString(), false);

		if (currCell.registeredQueries != null) {
			MovingObject q;
			for (int i : currCell.registeredQueries) {
				q = this.objects.get(i);
				if (q.contains(mo.getCenterP()) && q.oid != mo.oid) {
					q.addResultObject(mo.oid);
					mo.addSatisfiedQuery(i);
				}

				if (mo.contains(q.getCenterP()) && q.oid != mo.oid) {
					mo.currResult.add(q.oid);
					q.addSatisfiedQuery(mo.oid);
				}
			}
		}
	}

	void handleFocalMove(MovingObject mo) {
		HashSet<Cell> affectedCells = field.intersectWith(mo);
		boolean sendMsg = false;

		// minus phase: remove mo's which no longer satisfy the query
		for (Cell c : mo.fullCells) {
			if (!affectedCells.contains(c)) {
				mo.currResult.removeAll(c.registeredObjects);
				for (int k : c.registeredObjects) {
					objects.get(k).satisfiedQueries.remove(mo.oid);
				}
				c.registeredQueries.remove(mo.oid);
			}
		}
		for (Cell c : mo.partialCells) {
			if (!affectedCells.contains(c)) {
				for (int k : c.registeredObjects) {
					if (mo.currResult.contains(k)) {
						mo.currResult.remove(k);
						objects.get(k).satisfiedQueries.remove(mo.oid);
					}
				}
				c.registeredQueries.remove(mo.oid);
			}
		}

		// plus phase: add objects in newly overlapped cells
		for (Cell c : affectedCells) {
			c.addQuery(mo.oid);
			// c.registeredQueries.add(mo.oid);
			if (mo.contains(c)) {
				if (mo.fullCells.contains(c)) {
					// case 1: from contain to contain
					// do nothing
				} else {
					// case 2: from partial to contain
					if (mo.partialCells.contains(c)) {
						// update partial and full
						mo.partialCells.remove(c);
						mo.fullCells.add(c);

						// update result
						mo.currResult.addAll(c.registeredObjects);
						mo.currResult.remove(mo.oid);

						for (int k : c.registeredObjects) {
							if (k != mo.oid) {
								this.objects.get(k).addSatisfiedQuery(mo.oid);
							}
						}

					} else {
						// case 3: from non-overlap to contain

						// request the mo in the cell
						sendMsg = true;
						mo.currResult.addAll(c.registeredObjects);
						mo.currResult.remove(mo.oid);

						for (int k : c.registeredObjects) {
							if (k != mo.oid) {
								this.objects.get(k).addSatisfiedQuery(mo.oid);
							}
						}

						// update full cell list
						mo.fullCells.add(c);
					}
				}

			} else if (mo.intersects(c)) {
				if (mo.fullCells.contains(c)) {
					// case 1: from contain to intersect
					// remove some objects
					// update partial and full list
					mo.fullCells.remove(c);
					mo.partialCells.add(c);

					// update result
					for (int k : c.registeredObjects) {
						if (!mo.contains(objects.get(k).getCenterP())) {
							mo.currResult.remove(k);
							objects.get(k).satisfiedQueries.remove(mo.oid);
						}
					}
				} else if (mo.partialCells.contains(c)) {
					// case 2: from intersect to intersect
					// update result
					for (int k : c.registeredObjects) {
						// plus
						if (mo.contains(objects.get(k).getCenterP())
								&& !mo.currResult.contains(k) && mo.oid != k) {
							mo.currResult.add(k);
							objects.get(k).satisfiedQueries.add(mo.oid);

						}
						// minus
						if (!mo.contains(objects.get(k).getCenterP())
								&& mo.currResult.contains(k)) {
							mo.currResult.remove(k);
							objects.get(k).satisfiedQueries.remove(mo.oid);
						}
					}

				} else {
					// case 3: from non-overlap to intersect
					sendMsg = true;
					mo.partialCells.add(c);
					for (int k : c.registeredObjects) {
						if (mo.contains(objects.get(k).getCenterP())
								&& k != mo.oid) {
							mo.currResult.add(k);
							objects.get(k).satisfiedQueries.add(mo.oid);
						}
					}
				}
			}
		}

		//TODO update the eventQ
		
		if (sendMsg) {
			Msg_o2s_focalMove o2smfm = new Msg_o2s_focalMove(currentTS, mo.oid,
					mo.getCenterP());
			Msg_s2o_focalMove s2omfm = new Msg_s2o_focalMove(currentTS);
			this.putMsgIntoMap(currentTS, o2smfm.toString(), true);
			this.putMsgIntoMap(currentTS, s2omfm.toString(), false);
		}
	}

	/**
	 * When moving object velocity changes, the event queue of affected queries
	 * has to be updated.
	 * 
	 * @param qmo
	 * @param newv
	 * @param oldv
	 */
	public void handleMOVelocityChange(MovingObject qmo, Velocity oldv,
			Velocity newv) {
		if (qmo.satisfiedQueries != null && qmo.satisfiedQueries.size() != 0) {
			// send message to server
			Msg_o2s_velocityChange movc = new Msg_o2s_velocityChange(
					this.currentTS, qmo.oid, qmo.getCenterP());
			this.putMsgIntoMap(this.currentTS, movc.toString(), true);

			// System.out.println("o2s velocity change time: " + currentTS);

			MovingObject q;
			for (int k : qmo.satisfiedQueries) {
				q = this.objects.get(k);
				Msg_s2o_velocityChange movcs2o = new Msg_s2o_velocityChange(
						currentTS);
				this.putMsgIntoMap(this.currentTS, movcs2o.toString(), false);
				q.handleResultMoVelocityChange(this.currentTS, qmo, oldv, newv);
			}
		}
		//TODO update eventQ here
	}

	/**
	 * When focal velocity changes, the event queue has to be updated No message
	 * passing is needed.
	 * 
	 * @param timeStamp
	 * 
	 * @param qmo
	 */
	public void handleFocalVelocityChange(int timeStamp, MovingObject qmo) {
		// remove all events having qmoid as from moid => list
		// modify the events in the list
		// insert back the list
		if (timeStamp == 1) {
			// add exit event that will happen in future
			MovingObject tempmo;
			for (int k : qmo.currResult) {
				tempmo = this.objects.get(k);
				double dist = qmo.radius
						- tempmo.getCenterP().distance(qmo.getCenterP());
				double xVel = tempmo.getVelocity().xSpeed
						- qmo.getVelocity().xSpeed;
				double yVel = tempmo.getVelocity().ySpeed
						- qmo.getVelocity().ySpeed;
				double estSpeed = xVel + yVel;
				System.out.println("currt mo:" + qmo.oid + " result mo:"
						+ tempmo.oid);
				System.out.println("dist: " + dist + " estspeed:" + estSpeed);

				eventQ.insert(new MyEvent(dist / estSpeed, tempmo, qmo,
						MyEventType.exit));
			}
			// add enter into event list

			for (Cell c : qmo.partialCells) {
				for (int i : c.registeredObjects) {
					if (!qmo.currResult.contains(i)) {
						tempmo = objects.get(i);
						Velocity v1 = tempmo.getVelocity();
						Velocity v2 = qmo.getVelocity();

						double relativeX = Math.abs(v1.xSpeed - v2.xSpeed);
						double relativeY = Math.abs(v1.ySpeed - v2.ySpeed);
						// estimate the time next enter event will happen
						double dist = qmo.getCenterP().distance(
								tempmo.getCenterP())
								- qmo.radius;
						// speed estimator is the summation of the xspeed and
						// yspeed
						// the time is soonest time the event can happen
						double estSpeed = relativeX + relativeY;

						eventQ.insert(new MyEvent(this.currentTS + dist
								/ estSpeed, tempmo, qmo, MyEventType.enter));
					}
				}

			}
		} else {
			LinkedList<MyEvent> specificEvents = eventQ
					.removeWithFromID(qmo.oid);

			for (MyEvent e : specificEvents) {
				// adjust time based on speed and time
				double dist = qmo.getCenterP().distance(e.mo_to.getCenterP());
				double relativeSpeed = Math.abs(qmo.speed.xSpeed
						- e.mo_to.speed.xSpeed)
						+ Math.abs(qmo.speed.ySpeed - e.mo_to.speed.ySpeed);
				e.setTimeToHappen(dist / relativeSpeed);
			}

			// reinsert inyo the eventQ
			for (MyEvent e : specificEvents) {
				eventQ.insert(e);
			}
		}
	}

	/**
	 * Server initial processing
	 */
	public void init(int radius) {
		this.loadinObjects(radius);
		this.initialProcess();
		System.out.println("After initialProcess: ");
		for (int k : objects.keySet()) {
			MovingObject mo = objects.get(k);
			System.out.println("mo"+k + ": " + mo.currResult + "; "
					+ mo.satisfiedQueries);
		}

		// System.out.println("query and cell relation: ");

		this.prepareMsgFile();

		// continuous processing
		while (!this.end) {
			this.currentTS++;
			// read t_(i+1) data at time t_i and
			// check whose velocity change
			this.computeVelocity();
			System.out.println("time" + currentTS + " event q: " + eventQ);
			// update result
			this.processNTS();
			// write out messages.
			this.writeOutO2sMsgs();

			// this.sampleObjectsWorkload();

			System.out.println("ts:" + this.currentTS);
			for (int k : objects.keySet()) {
				MovingObject mo = objects.get(k);

				System.out.println(k + ": " + mo.currResult + "; "
						+ mo.satisfiedQueries);
			}
		}
		this.writeOutStatistics();
		this.printStatisticsToScreen();

	}

	/**
	 * 
	 * Compute initial results for the queries <br>
	 * go by query; retrieve objects in overlapping cells <br>
	 * send appropriate messages <br>
	 */
	public void initialProcess() {
		// for each query
		for (Integer i : this.objects.keySet()) {
			// System.out.println("Init query " + i);
			MovingObject qo = (MovingObject) this.objects.get(i);

			// o2s to install queries
			Msg_o2s_installQuery miq = new Msg_o2s_installQuery(this.currentTS,
					qo.oid, qo.getCenterP(), qo.getVelocity(), qo.currC);
			this.putMsgIntoMap(this.currentTS, miq.toString(), true);

			// s2o results messages
			Msg_s2o_queryResult miqs2o = new Msg_s2o_queryResult(currentTS);
			this.putMsgIntoMap(this.currentTS, miqs2o.toString(), false);

			// for each overlapping cell,if these mo satisfy the query, add
			// registered mo into result
			MovingObject tempmo;
			if (qo.fullCells != null) {
				for (Cell c : qo.fullCells) {
					// add all objects in these cells into qo
					qo.currResult.addAll(c.registeredObjects);
					// add qo into every object's result
					for (int k : c.registeredObjects) {
						if (k != i) {
							tempmo = this.objects.get(k);
							tempmo.addSatisfiedQuery(qo.oid);
						}
					}
					// register the query with the cell
					c.registeredQueries.add(qo.oid);
				}
			}

			// handle partial cells
			for (Cell c : qo.partialCells) {
				// register the query with the cell
				c.registeredQueries.add(qo.oid);

				for (int k : c.registeredObjects) {
					tempmo = this.objects.get(k);
					// mo is in the result set or
					// mo is put into the event list
					if (qo.contains(tempmo.getCenterP())
							&& qo.oid != tempmo.oid) {

						// do not add itself
						qo.addResultObject(k);
						tempmo.addSatisfiedQuery(i);
					}
				}
			}
			// remove itself from the result
			qo.currResult.remove(qo.oid);
			qo.satisfiedQueries.remove(qo.oid);
			// update the eventq
			// time2happen is set to -1 to indicate it is not known yet
			for (int k : qo.currResult) {
				eventQ.insert(new MyEvent(-1, qo, this.objects.get(k),
						MyEventType.exit));
			}
		}
	}

	/**
	 * Create moving objects and moving queries from the data.
	 */
	public void loadinObjects(int radius) {
		this.currentTS = 0;
		double maxWidth = 0;
		double maxHeight = 0;
		String[] aORecord = this.getOneObjectRecord();
		assert (aORecord != null) : "in time0process, get null record";
		int readTS = Integer.parseInt(aORecord[4]);
		while (aORecord != null && readTS == 0) {
			int oid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);
			if (px > maxWidth) {
				maxWidth = px;
			}
			if (py > maxHeight) {
				maxHeight = py;
			}

			Point2D p = new Point2D.Double(px, py);
			Cell cell = this.field.inWhichCell(p);
			MovingObject mo = new MovingObject(oid, radius, p, new Velocity(0,
					0), 0, cell, this.field);
			this.objects.put(new Integer(mo.oid), mo);

			// cell register object here
			cell.registeredObjects.add(mo.oid);

			// cell register queries here
			HashSet<Cell> affectedCells = field.intersectWith(mo);

			for (Cell c : affectedCells) {
				c.addQuery(mo.oid);
			}

			// query register full cell and partial cells
			for (Cell c : affectedCells) {
				if (mo.contains(c)) {
					mo.addFullCells(c);
				} else {
					mo.addPartCells(c);
				}
			}

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
		this.numObjects = this.objects.size();
		System.err.println("Max:" + maxWidth + "," + maxHeight);
		System.err.println("# of objects:" + this.numObjects);
	}

	public MessageGeneratorCentral(int numCellsPerRow, int numCellsPerCol,
			int cellLength, String objectFile, int radius, String speedtag) {
		this.cellLength = cellLength;
		this.field = new Field(numCellsPerRow, numCellsPerCol, cellLength);
		this.objects = new HashMap<Integer, MovingObject>();
		// this.queryTable = new HashMap<Integer, QueryTableEntry>();

		this.speedTag = speedtag;

		this.timeMsgMap_o2s = new TreeMap<Integer, ArrayList<String>>();
		this.timeMsgMap_s2o = new TreeMap<Integer, ArrayList<String>>();

		this.stat = new Statistics();
		this.end = false;
		this.enterNewCellMo = new HashSet<Integer>();
		eventQ = new MyEventQSorted();

		// file handle
		this.ofilePath = new File(objectFile);
		try {
			FileReader ofr = new FileReader(this.ofilePath);
			this.obr = new BufferedReader(ofr);
		} catch (FileNotFoundException e) {
			System.out
					.println("In MessageGenerator(), file not found exception: "
							+ this.ofilePath);
			System.exit(-1);
		}

		this.init(radius);
	}

	public static void main(String[] args) {

		String filepath_Oldenburg_100k_slow = "D:/research/network_data/test3mo.txt";
		// String q_Oldenburg_5k_slow = "D:/Research/data_gen/dismknn/q10k.txt";

		String speedTag;
		String o_dataset = filepath_Oldenburg_100k_slow;

		// parameters for oldenburg data
		int oldenCellLength = 400;
		int olden_numCellsRow = 60;
		int olden_numCellsCol = 80;
		int olden_radius = 2000; // use the same radius for every mo

		// test parameters (10x10 grids)
		int test_CellLength = 1;
		int test_numCellsRow = 10;
		int test_numCellsCol = 10;
		int test_radius = 3;

		speedTag = "slow";

		// System.exit(-1);

		System.out.println("o dataset: " + o_dataset);
		// MessageGenerator mg = new MessageGenerator(olden_numCellsRow,
		// olden_numCellsCol, oldenCellLength, o_dataset, olden_radius,
		// speedTag);

		MessageGeneratorCentral mg1 = new MessageGeneratorCentral(
				test_numCellsRow, test_numCellsCol, test_CellLength, o_dataset,
				test_radius, speedTag);
	}

	/**
	 * move a moving object to a new location nextP
	 * 
	 * @param nextP
	 * 
	 */
	void move(MovingObject mo, Point2D nextP) {
		mo.setCenter(nextP);
		mo.currC = field.inWhichCell(nextP);
	}

	public void prepareMsgFile() {
		// prepare msg output file
		String msgFileName = "o2sMsgs_o" + this.numObjects + "_c"
				+ this.cellLength + "_" + this.speedTag + ".txt";
		this.o2sMsgFilePath = new File(msgFileName);
		try {
			FileWriter o2sFw = new FileWriter(this.o2sMsgFilePath);
			this.o2sMsgBw = new BufferedWriter(o2sFw);
		} catch (IOException e) {
			System.out.println("In MessageGenerator(), IO exception");
			System.exit(-1);
		}
	}

	/**
	 * public void writeOutCells() { String cellFileName = "cellConfig_" +
	 * this.cellR + ".txt"; this.cellOutPath = new File(cellFileName); try {
	 * FileWriter cellFw = new FileWriter(this.cellOutPath); this.cbw = new
	 * BufferedWriter(cellFw); } catch (IOException e) {
	 * System.out.println("In MessageGenerator(), IO exception");
	 * System.exit(-1); }
	 * 
	 * for (int r = 1; r <= this.field.CMatrix.numRows; r++) { CellRow cr =
	 * this.field.CMatrix.getRow(r); for (int c = 1; c < cr.cellColumns.size();
	 * c++) { Cell cell = (Cell) cr.cellColumns.get(c); String ps = cell.cid.row
	 * + " " + cell.cid.column + " " + cell.center.x + " " + cell.center.y + " "
	 * + cell.sideLength; try { this.cbw.write(ps + "\n"); } catch (IOException
	 * e) { System.out.println("In printAllObjects(), IO exception");
	 * System.exit(-1); } } }
	 * 
	 * try { this.cbw.flush(); } catch (IOException e) {
	 * System.out.println("In convert(), IO exception"); System.exit(-1); }
	 * 
	 * }
	 **/
	/**
	 * @return a String array representing a record; null when come to the end
	 *         of the file. read a record from the input file.
	 */

	public static void parseArgs(String[] args) {
		if (args.length > 0) {
			if (args.length != 5) {
				System.out.println("usage: o_dataset cellLength speedTag");
			} else {
				// o_dataset = args[0];
				// oldenCellLength = Integer.parseInt(args[1]);
				// speedTag = args[2];
			}
		}
	}

	public void parseLine(String line) {
		String[] subStrings = line.split("\t");
		// System.out.println(subStrings.length);
		for (int i = 0; i < subStrings.length; i++) {
			System.out.println(subStrings[i]);
		}
		if (subStrings.length == 10) {
			String recordTyppe = subStrings[0];
			System.out.println(recordTyppe);

			if (recordTyppe.equals("newpoint")) {
				System.out.println("a new point is generated, with oid "
						+ subStrings[1]);

			} else if (recordTyppe.equals("point")) {
				System.out.println("point " + subStrings[1] + " moves");

			} else if (recordTyppe.equals("disappearpoint")) {
				System.out.println("point " + subStrings[1] + " disappears");
			}
		}
	}

	/**
	 * Essential method to handle object movement for each query, update its
	 * result <br>
	 * 
	 */
	public void processNTS() {
		// mo moves
		MovingObject mo;

		// mq moves
		// 1. examine monitoring moving objects
		// 2. examine new cell's moving object
		for (int i : this.objects.keySet()) {
			mo = this.objects.get(i);
			mo.prevP = mo.getCenterP();
			move(mo, mo.nextP);
			handleFocalMove(mo);
		}

		// case 1: it enters a new cell
		// case 2: its velocity change significantly---handled in compute
		// velocity
		for (Integer i : enterNewCellMo) {
			mo = this.objects.get(i);
			this.handleEnterCell(mo, field.inWhichCell(mo.prevP), field
					.inWhichCell(mo.getCenterP()));
		}

	}

	public void putMsgIntoMap(int msgTime, String msgString, boolean incoming) {
		int key = msgTime;
		// System.out.println("msg time; " + msgTime);
		// System.out.println(this.timeMsgMap_o2s.keySet());
		ArrayList<String> msgList = null;
		if (this.timeMsgMap_o2s.containsKey(msgTime)) {
			msgList = this.timeMsgMap_o2s.get(key);
			msgList.add(msgString);
		} else {
			msgList = new ArrayList<String>();
			msgList.add(msgString);
		}
		if (incoming) {
			this.timeMsgMap_o2s.put(key, msgList);
			this.stat.numO2sMessages++;
		} else {
			this.timeMsgMap_s2o.put(key, msgList);
			this.stat.numS2oMessages++;
		}
	}

	//
	// public void processNTS() {
	// for (int qid : q_objects.keySet()) {
	// // System.out.println(this.queryTable.get(qid));
	// double pre_event_time = 0;
	// Msg_o2s_criticalObjectChange event = this.aQuerysNextEvent(qid,
	// pre_event_time);
	// while (event != null) {
	// // System.out.println(event);
	// this.putMsgIntoMap(event.timepoint, event.toString());
	//
	// event = this.aQuerysNextEvent(qid, event.timepoint
	// - this.currentTS);
	// }
	// }
	// }

	/**
	 * @param qid
	 * @param pre_event_time
	 * @return -1 if no critical object event will happen between pre_event_time
	 *         and 1; otherwise the time point of this event. Note that the
	 *         event is processed in this method.
	 */
	// public Msg_o2s_criticalObjectChange aQuerysNextEvent(int qid,
	// double pre_event_time) {
	// Msg_o2s_criticalObjectChange mcoc = null;
	// QueryTableEntry qte = (QueryTableEntry) this.queryTable
	// .get(new Integer(qid));
	// int co_id = qte.criticalOid;
	// double earlist = 1.00001;
	// int causingOid = 0;// todo: is it ok to init it as 0?
	// for (CellID aCid : qte.affectedCells) {
	// Cell aCell = this.getCell(aCid);
	// for (int oid : aCell.registeredObjects) {
	// if (oid != co_id) {
	// double eventTime = this.estimateCriticalChangeTime(qid,
	// co_id, oid, pre_event_time);
	// if (eventTime != -1 && eventTime > pre_event_time + 0.00001) {
	// if (eventTime < earlist) {
	// earlist = eventTime;
	// causingOid = oid;
	// } else if (eventTime == earlist) {
	// System.err.println("simultaneous events detected");
	// }
	// }
	// }
	// }
	// }
	// if there is an event, process
	// if (earlist < 1.00001) {
	// MovingObject causingMO = this.getObject(causingOid);
	// // calculate the position of the causingMO at this time
	// Location eventLoc = causingMO.locationAfterTime(earlist);
	// // TODOne: o2s critical change message
	// mcoc = new Msg_o2s_criticalObjectChange(this.currentTS + earlist,
	// qid, causingOid, eventLoc, causingMO.v, co_id);
	// }
	// return mcoc;
	// }

	// /**
	// * update objects' cells.
	// */
	// public void postProcessNTS_O() {
	// // todo: what is done here?
	// // todo: bug: cell is updated only at the end of a time unit
	// for (Integer i : objects.keySet()) {
	// MovingObject mo = (MovingObject) this.objects.get(i);
	// if (mo.nextP != null) {
	// CellID cid = this.field.inWhichCell(mo.nextP);
	// if (!mo.registeredCell.equals(cid)) {
	// // un-register the mo from previsous cell
	// CellID pre_cid = mo.registeredCell;
	// Cell pre_cell = this.getCell(pre_cid);
	// pre_cell.unregister(mo.oid);
	//
	// // test whether fly happens
	// assert (pre_cell.neighboringCells.contains(cid)) :
	// "in postProcessNTS_O, the object flies into a non-neighboring cell";
	// // register it to the new cell
	// Cell cur_cell = this.getCell(cid);
	// cur_cell.register(mo.oid);
	// // delta queries, minus, plus
	// mo.registeredCell = cid;
	// // TODOne: o2s cell change message
	// int fromObject = 0;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromObject, mo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// this.handleEnterCell(mo.oid, pre_cid, cid);
	// }
	// mo.currP = mo.nextP;
	// }
	// }
	// }

	// /**
	// * update queries' cells.
	// */
	// public void postProcessNTS_Q() {
	// for (QueryTableEntry qte : this.queryTable.values()) {
	// int qoid = qte.focalOid;
	// MovingObject qmo = this.getQ_object(qoid);
	// if (qmo.nextP != null) {
	// CellID cid = this.field.inWhichCell(qmo.nextP);
	// if (!qmo.registeredCell.equals(cid)) {
	// CellID pre_cid = qmo.registeredCell;
	// int fromQuery = 1;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromQuery, qmo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// qmo.registeredCell = cid;
	// }
	// qmo.currP = qmo.nextP;
	// }
	// }
	// }

	// public void NTS() {
	// this.processNTS();
	// this.postProcessNTS_O();
	// this.postProcessNTS_Q();
	// this.currentTS++;
	// this.writeOutO2sMsgs();
	// this.sampleObjectsWorkload();
	// }

	// public void continuousNTS() {
	// while (!this.end) {
	// this.NTS();
	// System.err.println("ts:" + this.currentTS);
	// }
	// this.stat.calculateStatistics(this.objects.size(),
	// this.q_objects.size(), this.currentTS);
	// this.writeOutStatistics();
	// this.printStatisticsToScreen();
	// }

	public void printStatisticsToScreen() {
		String seperator = "\t";
		String s = "MCQ" + seperator;
		s = s + this.numObjects + seperator;
		s = s + this.cellLength + seperator;
		s = s + this.speedTag + seperator;
		s = s + this.stat.numO2sMessages + seperator;
		s = s + this.stat.numS2oMessages + seperator;
		s = s + this.stat.getTotalMsgRate(currentTS);
		System.out.println(s);
	}

	// public void sampleObjectsWorkload() {
	// for (int i = 0; i < this.objects.size(); i++) {
	// MovingObject mo = this.getObject(i);
	// this.stat.numQueriesByObjects = this.stat.numQueriesByObjects
	// + mo.monitoringQueries.size();
	// }
	// }

	// public void checkACellForNearerObjects(CellID cid, int qid,
	// Location focolLoc, boolean withCD, double distance,
	// DisObjsMap disObjMap) {
	// Cell cell = this.field.CMatrix.getCell(cid);
	// for (int oid : cell.registeredObjects) {
	// MovingObject mo = this.getObject(oid);
	// if (withCD) {
	// double nearerDis = mo.nearer(focolLoc, distance);
	// if (nearerDis > 0) {
	// disObjMap.put(nearerDis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// } else {
	// double dis = mo.currP.distance(focolLoc);
	// if (dis == 0) {
	// System.err
	// .println("the object is at the same location as the query object");
	// }
	// if (disObjMap.containsKey(new Double(dis))) {
	// System.err
	// .println("objects with a same distance to the query object are found");
	// }
	// disObjMap.put(dis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// }
	//
	// }

	// public void printObjectsOwnQuery() {
	// for (int i = 0; i < this.numObjects; i++) {
	// MovingObject mo = (MovingObject) this.objects.get(new Integer(i));
	// mo.printOwnQueries();
	// }
	// }

	public void printQT() {
		System.out.println("======================");
		System.out.println("==========QT==========, at time " + this.currentTS);
		// for (int i = 0; i < this.queryTable.size(); i++) {
		// QueryTableEntry qte = (QueryTableEntry) this.queryTable
		// .get(new Integer(i));
		// System.out.println(qte);
		// }
	}

	public void printObjects() {
		System.out.println("======================");
		System.out.println("==========Moving Objects==========, at time "
				+ this.currentTS);
		for (int i = 0; i < this.objects.size(); i++) {
			MovingObject mo = this.getObject(i);
			System.out.println(mo);
		}
	}

	public void printQueryObjects() {
		System.out.println("======================");
		System.out.println("==========Query Objects==========, at time "
				+ this.currentTS);
		for (int i = 0; i < this.objects.size(); i++) {
			MovingObject mo = this.objects.get(i);
			System.out.println(mo);
		}
	}

	/**
	 * write out the o2s messages. This also makes the message queue empty.
	 */
	public void writeOutO2sMsgs() {
		for (int key : this.timeMsgMap_o2s.keySet()) {
			ArrayList<String> msgs = this.timeMsgMap_o2s.get(key);
			for (String msg : msgs) {
				try {
					this.o2sMsgBw.write(msg + "\n");
				} catch (IOException e) {
					System.out.println("In writeOutO2sMsgs(), IO exception");
					System.exit(-1);
				}
			}
		}

		this.timeMsgMap_o2s.clear();

		try {
			this.o2sMsgBw.flush();
		} catch (IOException e) {
			System.out.println("In writeOutO2sMsgs(), IO exception");
			System.exit(-1);
		}
	}

	public void writeOutStatistics() {

		String seperator = "\n";
		String s = "no:\t" + this.numObjects + "\n";
		s = s + "nq:\t" + this.numQueries + "\n";
		s = s + "cell r:\t" + this.cellLength + "\n";
		s = s + "ts:\t" + this.currentTS + "\n";
		s = s + "speed:\t" + this.speedTag + "\n";
		s = s + this.stat.numO2sMessages + seperator;
		s = s + this.stat.numS2oMessages + seperator;
		s = s + this.stat.getTotalMsgRate(currentTS);

		// write statistics out
		try {
			FileWriter stat_fw = new FileWriter("statistics.txt", true);
			BufferedWriter sbw = new BufferedWriter(stat_fw);
			sbw.write(s);
			sbw.flush();
		} catch (IOException e) {
			System.out.println("In MessageGenerator(), IO exception");
			System.exit(-1);
		}

	}

}
